/*
 * @(#)RegistryObjectsTransformer.java
 * Date 2013-04-10
 * Version 1.0
 * SubmissionSetAuthor Jim Horner
 * Copyright (c)2012
 */


package us.hornerscorners.lamppost.registry.transformer.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.enterprise.context.ApplicationScoped;

import javax.inject.Inject;

import javax.xml.bind.JAXBElement;

import oasis.names.tc.ebxml_regrep.xsd.lcm._3.SubmitObjectsRequest;

import oasis.names.tc.ebxml_regrep.xsd.rim._3.AssociationType1;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.ClassificationType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.ExtrinsicObjectType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.IdentifiableType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.ObjectRefType;
import oasis.names.tc.ebxml_regrep.xsd.rim._3.RegistryPackageType;

import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import us.hornerscorners.lamppost.exception.ServiceActionError;
import us.hornerscorners.lamppost.exception.ServiceActionException;
import us.hornerscorners.lamppost.exception.SeverityType;
import us.hornerscorners.lamppost.exception.ValidationException;

import us.hornerscorners.lamppost.hl7v3common.utils.HL7Utils;

import us.hornerscorners.lamppost.ihecommon.config.IHEConfig;

import us.hornerscorners.lamppost.ihecommon.service.JsonMarshaller;

import us.hornerscorners.lamppost.model.common.CodedElement;
import us.hornerscorners.lamppost.model.common.InstanceIdentifier;
import us.hornerscorners.lamppost.model.common.PersistableModelType;

import us.hornerscorners.lamppost.model.document.Association;
import us.hornerscorners.lamppost.model.document.DocumentEntry;
import us.hornerscorners.lamppost.model.document.DocumentType;
import us.hornerscorners.lamppost.model.document.Folder;
import us.hornerscorners.lamppost.model.document.IAssociable;
import us.hornerscorners.lamppost.model.document.SubmissionSet;

import us.hornerscorners.lamppost.model.patient.PatientIdentity;

import us.hornerscorners.lamppost.registry.constant.AssociationType;
import us.hornerscorners.lamppost.registry.constant.AvailabilityStatus;
import us.hornerscorners.lamppost.registry.constant.ErrorCode;
import us.hornerscorners.lamppost.registry.constant.Metadata;

import us.hornerscorners.lamppost.registry.exception.RegistryException;

import us.hornerscorners.lamppost.registry.model.IncomingRegistryObjects;
import us.hornerscorners.lamppost.registry.model.RegistryObjects;

import us.hornerscorners.lamppost.registry.service.IPatientIdentityService;
import us.hornerscorners.lamppost.registry.service.IRegistryQueryService;

import us.hornerscorners.lamppost.registry.utils.XDSUtils;

import us.hornerscorners.lamppost.transformer.AbstractTransformer;


/**
 *
 * @author jim
 */
@ApplicationScoped
public class RegistryObjectsTransformer
        extends AbstractTransformer<SubmitObjectsRequest, RegistryObjects> {

    /** Field description */
    @Inject
    private AssociationTransformer associationTransformer;

    /** Field description */
    @Inject
    private CodedElementTransformer codedElementTransformer;

    /** Field description */
    @Inject
    private IHEConfig config;

    /** Field description */
    @Inject
    private DocumentEntryTransformer documentEntryTransformer;

    /** Field description */
    @Inject
    private FolderTransformer folderTransformer;

    /** Field description */
    @Inject
    private JsonMarshaller jsonMarshaller;

    /** Field description */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Field description */
    @Inject
    private IPatientIdentityService patientIdentityService;

    /** Field description */
    @Inject
    private IRegistryQueryService queryService;

    /** Field description */
    @Inject
    private SubmissionSetTransformer submissionSetTransformer;

    /**
     * Constructs ...
     *
     */
    public RegistryObjectsTransformer() {
        super();
    }

    /**
     * Method description
     *
     *
     * @param sor
     *
     * @return
     * @throws us.hornerscorners.lamppost.exception.ValidationException
     */
    protected IncomingRegistryObjects parseIdentifiableTypes(SubmitObjectsRequest sor)
            throws ValidationException {

        IncomingRegistryObjects result = new IncomingRegistryObjects();

        // Register Document
        for (JAXBElement<? extends IdentifiableType> jaxbelem :
                sor.getRegistryObjectList().getIdentifiable()) {

            IdentifiableType idtype = jaxbelem.getValue();

            if (idtype instanceof ExtrinsicObjectType) {

                ExtrinsicObjectType eo = (ExtrinsicObjectType) idtype;

                if (XDSUtils.isDocument(eo)) {

                    validateIncomingId(result, idtype);
                    result.addDocumentEntry(eo);
                }

            } else if (idtype instanceof RegistryPackageType) {

                RegistryPackageType regpack = (RegistryPackageType) idtype;

                if (XDSUtils.isFolder(regpack)) {

                    validateIncomingId(result, idtype);
                    result.addFolder(regpack);

                } else if (XDSUtils.isSubmissionSet(regpack)) {

                    if (result.getSubmissionSet() != null) {

                        String msg = "Too submission sets.";

                        ServiceActionError error =
                            new ServiceActionError(
                                ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                                SeverityType.Error);

                        throw new ValidationException(getErrorLocation(), error);
                    }

                    validateIncomingId(result, idtype);
                    result.setSubmissionSet(regpack);

                } else {

                    validateIncomingId(result, idtype);
                    result.addRegistryPackage(regpack);
                }

            } else if (idtype instanceof ClassificationType) {

                validateIncomingId(result, idtype);
                result.addClassification((ClassificationType) idtype);

            } else if (idtype instanceof AssociationType1) {

                validateIncomingId(result, idtype);
                result.addAssociation((AssociationType1) idtype);

            } else if (idtype instanceof ObjectRefType) {

                validateIncomingId(result, idtype);
                result.addObjectRef((ObjectRefType) idtype);
            }
        }

        String subsetClassURN = Metadata.SubmissionSetClassificationNode.getURN();
        String folderClassURN = Metadata.FolderClassificationNode.getURN();

        // go through classifications
        for (ClassificationType ctype : result.getClassifications()) {

            String key = ctype.getClassifiedObject();
            RegistryPackageType regpack = result.findRegistryPackage(key);

            if (regpack != null) {

                String cnode = ctype.getClassificationNode();

                if (StringUtils.isNotBlank(cnode)) {

                    if (subsetClassURN.equals(cnode)) {

                        if (result.getSubmissionSet() != null) {

                            String msg =
                                "Only one submission set can be sent in request; more than one was received.";

                            ServiceActionError error =
                                new ServiceActionError(
                                    ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                                    SeverityType.Error);

                            throw new ValidationException(getErrorLocation(), error);
                        }

                        regpack.getClassification().add(ctype);
                        result.setSubmissionSet(regpack);

                    } else if (folderClassURN.equals(cnode)) {

                        regpack.getClassification().add(ctype);
                        result.addFolder(regpack);
                    }
                }
            }
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param folders
     *
     * @return
     */
    private Collection<Folder> retrieveFolders(List<Association> folders)
            throws ServiceActionException {

        List<Folder> result = new ArrayList<>();
        List<String> entryURNs = new ArrayList<>();

        for (Association folder : folders) {

            entryURNs.add(folder.getSourceEntryURN());
        }

        result.addAll(Arrays.asList(this.queryService.retrieveFolders(entryURNs)));

        for (Folder folder : result) {

            folder.saveStateToPreviousVersion(this.jsonMarshaller);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param parentDoc
     *
     * @return
     */
    private Collection<DocumentEntry> retrieveRelatedDocumentsAndDeprecate(
            DocumentEntry parentDoc)
            throws ServiceActionException {

        List<DocumentEntry> result = new ArrayList<>();

        result.addAll(
            Arrays.asList(
                this.queryService.retrieveRelatedDocuments(parentDoc.getEntryURN())));

        CodedElement availStatus =
            this.codedElementTransformer.transform(AvailabilityStatus.Deprecated);

        for (DocumentEntry entry : result) {

            entry.saveStateToPreviousVersion(this.jsonMarshaller);
            entry.setAvailabilityStatus(availStatus);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param sor
     *
     * @return
     * @throws us.hornerscorners.lamppost.exception.ServiceActionException
     *
     */
    @Override
    public RegistryObjects transform(SubmitObjectsRequest sor)
            throws ServiceActionException {

        IncomingRegistryObjects incoming = parseIdentifiableTypes(sor);

        RegistryObjects result = transformIdentifiableTypes(incoming);

        InstanceIdentifier pid = result.getSubmissionSets()[0].getPatientId();
        PatientIdentity patientIdentity =
            this.patientIdentityService.retrievePatientIdentity(pid);

        if ((patientIdentity == null) || (patientIdentity.getDateSubsumed() != null)) {

            String msg =
                String.format("Patient identifier [%s] is not known to this registry.",
                              HL7Utils.toCXString(pid));
            ServiceActionError error =
                new ServiceActionError(ErrorCode.XDSUnknownPatientId.getCode(), msg,
                                       SeverityType.Error);

            throw new ValidationException(getClass(), error);
        }

        result.setPatientIdentity(patientIdentity);
        result.addAll(transformObjectRefs(incoming, patientIdentity));

        transformAssociationTypes(incoming, result);

        return result;
    }

    /**
     * Method description
     *
     *
     * @param incoming
     * @param rol
     * @throws us.hornerscorners.lamppost.exception.ServiceActionException
     *
     */
    protected void transformAssociationTypes(IncomingRegistryObjects incoming,
            RegistryObjects rol)
            throws ServiceActionException {

        // need to order associations,
        // push ones that reference other associations towards the bottom
        List<AssociationType1> assocsOrdered = new ArrayList<>();

        assocsOrdered.addAll(Arrays.asList(incoming.getAssociations()));
        Collections.sort(
            assocsOrdered,
            new AssociationTypeOrderComparator(incoming.getAssociationKeys()));

        // go through associations
        for (AssociationType1 atype : assocsOrdered) {

            AssociationType type =
                AssociationType.toAssociationType(atype.getAssociationType());

            logger.debug("Association {} I{} S{} T{}", new Object[] { type, atype.getId(),
                    atype.getSourceObject(), atype.getTargetObject() });

            Association newAssoc = null;

            if (AssociationType.HasMember.equals(type)) {

                newAssoc = transformHasMemberAssociation(incoming, rol, atype);

            } else if (AssociationType.Append.equals(type)
                       || AssociationType.Transform.equals(type)
                       || AssociationType.Signs.equals(type)
                       || AssociationType.Replace.equals(type)
                       || AssociationType.TransformReplace.equals(type)) {

                String sourceId = atype.getSourceObject();
                String newSourceId = incoming.translateId(sourceId);
                boolean sourceByRef = rol.isReference(newSourceId);

                DocumentEntry sourceDoc = rol.findDocumentEntry(newSourceId);

                if (sourceDoc == null) {

                    String msg = String.format("Source document [%s] does not exist.",
                                               sourceId);

                    ServiceActionError error =
                        new ServiceActionError(
                            ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                            SeverityType.Error);

                    throw new ValidationException(getErrorLocation(), error);
                }

                if (AssociationType.Transform.equals(type)
                        || AssociationType.TransformReplace.equals(type)) {

                    sourceDoc.setDocumentType(DocumentType.Transformation);

                } else if (AssociationType.Append.equals(type)) {

                    sourceDoc.setDocumentType(DocumentType.Addendum);
                }

                // set parent on sourceDoc

                String targetId = atype.getTargetObject();
                String newTargetId = incoming.translateId(targetId);
                boolean targetByRef = rol.isReference(newTargetId);

                DocumentEntry parentDoc = rol.findDocumentEntry(newTargetId);

                if (parentDoc == null) {

                    parentDoc = this.queryService.retrieveDocumentEntry(targetId);

                    validatePatientIdentifier(PersistableModelType.DocumentEntry,
                                              rol.getPatientIdentity(), parentDoc);

                    rol.addDocumentEntry(parentDoc);
                    targetByRef = true;
                }

                if (parentDoc == null) {

                    String msg = String.format("Target document [%s] does not exist.",
                                               targetId);

                    ServiceActionError error =
                        new ServiceActionError(ErrorCode.XDSReplaceFailed.getCode(), msg,
                                               SeverityType.Error);

                    throw new ValidationException(getErrorLocation(), error);
                }

                newAssoc = this.associationTransformer.transformAssociation(atype,
                        sourceDoc, parentDoc, sourceByRef, targetByRef);

                sourceDoc.setParentDocument(newAssoc);

                if (XDSUtils.isNotApproved(parentDoc.getAvailabilityStatus())) {

                    String msg =
                        String.format(
                            "Target document [%s] is not in an 'Approved' status.",
                            targetId);

                    ServiceActionError error =
                        new ServiceActionError(
                            ErrorCode.XDSRegistryDeprecatedDocumentError.getCode(), msg,
                            SeverityType.Error);

                    throw new ValidationException(getErrorLocation(), error);
                }

                if (AssociationType.Replace.equals(type)
                        || AssociationType.TransformReplace.equals(type)) {

                    // update folder last mod date
                    for (Folder folder : retrieveFolders(parentDoc.getFolders())) {

                        rol.addFolder(folder);

                        Association folderAssoc =
                            this.associationTransformer.transformAssociation(parentDoc,
                                folder, sourceDoc, true, false);

                        sourceDoc.getFolders().add(folderAssoc);
                    }

                    parentDoc.saveStateToPreviousVersion(this.jsonMarshaller);

                    // need to deprecate parent
                    parentDoc.setAvailabilityStatus(
                        this.codedElementTransformer.transform(
                            AvailabilityStatus.Deprecated));

                    // deprecate everything connected to parent
                    rol.addAllDocumentEntries(
                        retrieveRelatedDocumentsAndDeprecate(parentDoc));

                } else {

                    if (DocumentType.Transformation.equals(sourceDoc.getDocumentType())
                            && AssociationType.Append.equals(type)) {

                        String msg =
                            String.format(
                                "Target document [%s] can not append a transformation document [%s].",
                                targetId);

                        ServiceActionError error =
                            new ServiceActionError(
                                ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                                SeverityType.Error);

                        throw new ValidationException(getErrorLocation(), error);
                    }

                    // if original is deprecated then deprecate this one
                    sourceDoc.setAvailabilityStatus(parentDoc.getAvailabilityStatus());
                }
            }

            if (newAssoc != null) {

                String key = incoming.translateId(incoming.getSubmissionSet().getId());
                SubmissionSet subSet = rol.findSubmissionSet(key);

                subSet.getSubmittedAssociations().add(newAssoc);

                rol.addAssociation(newAssoc);

                if (StringUtils.isNotBlank(atype.getId())) {

                    incoming.addTranslation(atype.getId(), newAssoc.getEntryURN());
                }
            }
        }

    }

    /**
     * Method description
     *
     *
     * @param incoming
     * @param rol
     *
     * @return
     *
     */
    private Association transformHasMemberAssociation(IncomingRegistryObjects incoming,
            RegistryObjects rol, AssociationType1 atype)
            throws ServiceActionException {

        Association result = null;

        // grab the source, either a submissionSet or Folder
        String sourceId = atype.getSourceObject();
        String newSourceId = incoming.translateId(sourceId);
        boolean sourceByRef = rol.isReference(newSourceId);

        SubmissionSet subSource = rol.findSubmissionSet(newSourceId);

        Folder folderSource = rol.findFolder(newSourceId);

        if ((subSource == null) && (folderSource == null)) {

            // search for an existing submission set
            if (XDSUtils.isNotSymbolicId(sourceId)) {

                subSource = this.queryService.retrieveSubmissionSet(sourceId);
            }

            // search for an existing folder
            if (XDSUtils.isNotSymbolicId(sourceId)) {

                folderSource = this.queryService.retrieveFolder(sourceId);
            }

            if (subSource != null) {

                subSource.saveStateToPreviousVersion(this.jsonMarshaller);

                rol.addSubmissionSet(subSource);
                sourceByRef = true;

            } else if (folderSource != null) {

                validatePatientIdentifier(PersistableModelType.Folder,
                                          rol.getPatientIdentity(), folderSource);

                folderSource.saveStateToPreviousVersion(this.jsonMarshaller);

                rol.addFolder(folderSource);
                sourceByRef = true;

            } else {

                String msg =
                    String.format("Association %s sourceObject %s does not exist.",
                                  atype.getId(), sourceId);

                ServiceActionError error =
                    new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(),
                                           msg, SeverityType.Error);

                throw new ValidationException(getErrorLocation(), error);
            }
        }

        // grab the target, either an association, documentEntry or Folder
        String targetId = atype.getTargetObject();
        String newTargetId = incoming.translateId(targetId);
        boolean targetByRef = rol.isReference(newTargetId);

        Association assocTarget = rol.findAssociation(newTargetId);

        DocumentEntry docTarget = rol.findDocumentEntry(newTargetId);

        Folder folderTarget = rol.findFolder(newTargetId);

        if ((assocTarget == null) && (folderTarget == null) && (docTarget == null)) {

            // search for an existing document
            if (XDSUtils.isNotSymbolicId(targetId)) {

                docTarget = this.queryService.retrieveDocumentEntry(targetId);
            }

            if (docTarget != null) {

                validatePatientIdentifier(PersistableModelType.DocumentEntry,
                                          rol.getPatientIdentity(), docTarget);

                docTarget.saveStateToPreviousVersion(this.jsonMarshaller);
                rol.addDocumentEntry(docTarget);
                targetByRef = true;

            } else {

                String msg =
                    String.format("Association %s targetObject %s does not exist.",
                                  atype.getId(), targetId);

                ServiceActionError error =
                    new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(),
                                           msg, SeverityType.Error);

                throw new ValidationException(getErrorLocation(), error);
            }
        }

        if (subSource != null) {

            // can take either assocTarget, folderTaret or docTarget
            if (assocTarget != null) {

                result = this.associationTransformer.transformSubmittedAssociation(atype,
                        subSource, assocTarget, sourceByRef, targetByRef);

            } else if (folderTarget != null) {

                result = this.associationTransformer.transformAssociation(atype,
                        subSource, folderTarget, sourceByRef, targetByRef);

                folderTarget.getSubmissionSets().add(result);

            } else if (docTarget != null) {

                result = this.associationTransformer.transformAssociation(atype,
                        subSource, docTarget, sourceByRef, targetByRef);

                docTarget.getSubmissionSets().add(result);
            }

        } else if (folderSource != null) {

            // can take docTarget
            if (docTarget == null) {

                String msg =
                    String.format("Association %s targetObject %s is not a document.",
                                  atype.getId(), targetId);

                ServiceActionError error =
                    new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(),
                                           msg, SeverityType.Error);

                throw new ValidationException(getErrorLocation(), error);
            }

            result = this.associationTransformer.transformAssociation(atype,
                    folderSource, docTarget, sourceByRef, targetByRef);

            docTarget.getFolders().add(result);
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param incoming
     *
     * @return
     *
     * @throws ServiceActionException
     */
    protected RegistryObjects transformIdentifiableTypes(IncomingRegistryObjects incoming)
            throws ServiceActionException {

        RegistryObjects result = new RegistryObjects();

        // Submission Set
        RegistryPackageType subRegPack = incoming.getSubmissionSet();

        if (subRegPack == null) {

            String msg = "A submission set is required.";

            ServiceActionError error =
                new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                                       SeverityType.Error);

            throw new ValidationException(getErrorLocation(), error);
        }

        SubmissionSet ss = null;

        try {

            ss = this.submissionSetTransformer.transform(subRegPack);

        } catch (ServiceActionException e) {

            throw new RegistryException(subRegPack, PersistableModelType.SubmissionSet,
                                        getErrorLocation(), e.getErrors());
        }

        result.addSubmissionSet(ss);

        InstanceIdentifier pid = ss.getPatientId();

        incoming.addTranslation(subRegPack.getId(), ss.getEntryURN());

        // Folders
        for (RegistryPackageType regpack : incoming.getFolders()) {

            try {

                Folder folder = this.folderTransformer.transform(regpack);

                validatePatientIdentifier(regpack.getId(), pid, folder);

                result.addFolder(folder);
                incoming.addTranslation(regpack.getId(), folder.getEntryURN());

            } catch (ServiceActionException e) {

                throw new RegistryException(regpack, PersistableModelType.Folder,
                                            getErrorLocation(), e.getErrors());
            }
        }

        // Document Entries
        for (ExtrinsicObjectType eo : incoming.getDocumentEntries()) {

            try {

                DocumentEntry docEntry = this.documentEntryTransformer.transform(eo);

                validateDocumentHash(eo.getId(), docEntry);
                validatePatientIdentifier(eo.getId(), pid, docEntry);

                result.addDocumentEntry(docEntry);
                incoming.addTranslation(eo.getId(), docEntry.getEntryURN());

            } catch (ServiceActionException e) {

                throw new RegistryException(eo, PersistableModelType.DocumentEntry,
                                            getErrorLocation(), e.getErrors());
            }
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param incoming
     *
     * @return
     *
     * @throws ServiceActionException
     */
    private RegistryObjects transformObjectRefs(IncomingRegistryObjects incoming,
            PatientIdentity patientIdentity)
            throws ServiceActionException {

        RegistryObjects result = new RegistryObjects();

        // Object References
        Set<String> objRefs = new HashSet<>();

        for (ObjectRefType objectRef : incoming.getObjectRefs()) {

            String objectRefId = objectRef.getId();

            if (objRefs.add(objectRefId) == false) {

                String msg = String.format("Object reference [%s] is repeated.",
                                           objectRefId);

                ServiceActionError error =
                    new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(),
                                           msg, SeverityType.Error);

                throw new ValidationException(getErrorLocation(), error);
            }

            objRefs.add(objectRefId);
        }

        if (objRefs.isEmpty() == false) {

            // Look up object references
            DocumentEntry[] documentEntries =
                this.queryService.retrieveDocumentEntries(objRefs);

            result.addAllDocumentEntries(Arrays.asList(documentEntries));

            Set<String> docRefs = new HashSet<>();

            for (DocumentEntry docEntry : documentEntries) {

                validatePatientIdentifier(PersistableModelType.DocumentEntry,
                                          patientIdentity, docEntry);

                docRefs.add(docEntry.getEntryURN());
            }

            result.addAllReferences(docRefs);
            objRefs.removeAll(docRefs);

            Folder[] folders = this.queryService.retrieveFolders(objRefs);
            Set<String> folderRefs = new HashSet<>();

            for (Folder folder : folders) {

                validatePatientIdentifier(PersistableModelType.Folder, patientIdentity,
                                          folder);

                folderRefs.add(folder.getEntryURN());
            }

            result.addAllReferences(folderRefs);
            objRefs.removeAll(folderRefs);

            if (objRefs.isEmpty() == false) {

                String msg =
                    String.format(
                        "Registry does not contain the following object references [%s].",
                        StringUtils.join(objRefs, ", "));

                ServiceActionError error =
                    new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(),
                                           msg, SeverityType.Error);

                throw new ValidationException(getErrorLocation(), error);
            }
        }

        return result;
    }

    /**
     * Method description
     *
     *
     * @param symbolicId
     * @param docEntry
     */
    private void validateDocumentHash(String symbolicId, DocumentEntry docEntry)
            throws ServiceActionException {

        DocumentEntry prevEntry =
            this.queryService.retrieveDocumentEntry(docEntry.getUniqueId());

        if ((prevEntry != null)) {

            if (prevEntry.getHash().equals(docEntry.getHash()) == false) {

                String msg =
                    String.format(
                        "Document [%s] hash [%s] does not match same document [%s] in registry with hash [%s].",
                        symbolicId, docEntry.getHash(),
                        prevEntry.getUniqueId().getRoot(), prevEntry.getHash());

                ServiceActionError error =
                    new ServiceActionError(ErrorCode.XDSNonIdenticalHash.getCode(), msg,
                                           SeverityType.Error);

                throw new ValidationException(getErrorLocation(), error);
            }
        }
    }

    /**
     * Method description
     *
     *
     * @param incoming
     * @param idtype
     *
     * @throws ValidationException
     */
    private void validateIncomingId(IncomingRegistryObjects incoming,
                                    IdentifiableType idtype)
            throws ValidationException {

        String id = idtype.getId();

        if (StringUtils.isBlank(id)) {

            String msg = String.format("%s is missing an identifier; symbolic or UUID",
                                       ClassUtils.getShortClassName(idtype.getClass()));

            ServiceActionError error =
                new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                                       SeverityType.Error);

            throw new ValidationException(getClass(), error);
        }

        if (incoming.hasIncomingId(id)) {

            String msg = String.format("The id [%s] is duplicated in the request.", id);

            ServiceActionError error =
                new ServiceActionError(ErrorCode.XDSRegistryMetadataError.getCode(), msg,
                                       SeverityType.Error);

            throw new ValidationException(getClass(), error);
        }
    }

    /**
     * Method description
     *
     *
     * @param modelType
     * @param patientIdentity
     * @param assoc
     *
     * @throws ValidationException
     */
    private void validatePatientIdentifier(PersistableModelType modelType,
            PatientIdentity patientIdentity, IAssociable assoc)
            throws ValidationException {

        if (patientIdentity.isNotSameId(assoc.getPatientId())) {

            String msg = String.format(
                             "%s objectref patient identifier [%s] does not match [%s].",
                             modelType.name(), HL7Utils.toCXString(assoc.getPatientId()),
                             HL7Utils.toCXString(patientIdentity.getIdentifier()));

            ServiceActionError error =
                new ServiceActionError(ErrorCode.XDSPatientIdDoesNotMatch.getCode(), msg,
                                       SeverityType.Error);

            throw new ValidationException(getClass(), error);
        }

    }

    /**
     * Method description
     *
     *
     * @param pid
     * @param assoc
     *
     * @throws ValidationException
     */
    private void validatePatientIdentifier(String symbolicId, InstanceIdentifier pid,
            IAssociable assoc)
            throws ValidationException {

        if (pid.isNotSameId(assoc.getPatientId())) {

            String msg =
                String.format(
                    "Entity [%s] contains patient identifier [%s] that does not match [%s].",
                    symbolicId, HL7Utils.toCXString(assoc.getPatientId()),
                    HL7Utils.toCXString(pid));

            ServiceActionError error =
                new ServiceActionError(ErrorCode.XDSPatientIdDoesNotMatch.getCode(), msg,
                                       SeverityType.Error);

            throw new ValidationException(getClass(), error);
        }
    }
}
